/*
 * @author Antonio Kantek
 * @version $Revision$ $Date$
 */
header {
	package org.estela.comp;

	import java.util.*;
}

options {
	language="Java"; 
}

////////////////////////
// Estela Lexer 
////////////////////////

/**
 *
 *
 */
class EstelaLexer extends Lexer;

options {
    testLiterals=false;
    k = 4; //lookahead for 2 tokens
    exportVocab=Estela;
    caseSensitive = true;
    caseSensitiveLiterals = false;
	charVocabulary = '\3' .. '\377'; //using ASCII characters
}

//defining keywords
tokens {
	BOOLEAN = "boolean";
	CLASSIFIER = "classifier";
	CONTINUE = "continue";
	DATASET = "dataset";
	DIM = "dim";
	DEFINE = "define";
    DOUBLE = "double";
    ELSE = "else";
	FALSE = "false";
	FOR = "for";
	FUNCTION = "function";
	IF = "if";
	INT = "int";
	IS = "is";
	INSTANCE = "instance";
	NaN = "NaN";
	NEW = "new";
	NOMINAL = "NOMINAL";
	NULL = "null";
	NUMERIC = "NUMERIC";
	SELF = "self";
	STRING = "string";
	TRUE = "true";
	VOID = "void";
}

RETURN: '^';

//Blocks specifiers
OPEN_BRACKET: '{';
CLOSE_BRACKET: '}';

ARRAY_OPEN:'[';
ARRAY_CLOSE: ']';

OPEN_PAREN: '(';
CLOSE_PAREN: ')';

//Logical operators
NOT: '~';
AND: "&&'";
OR: "||";

//Relational operators
EQUAL:	"==";
NOT_EQUAL:	"!=";
SEMI:	';';
COMMA:	',';
ASSIGN: ":=";
DOT_DOT: ':';


//More relational operators
protected GE:;

GT: '>' ( '=' { _ttype = GE; } )?
;

protected LE:;

LT: '<' ( '=' { _ttype = LE; } )?
;

//Whitespaces
WS: (   ' '
        |   '\t'
        |   '\r' '\n' { newline(); }
        |   '\n'      { newline(); }
        |   '\r'      { newline(); }
        )
        {$setType(Token.SKIP);} //ignore this token
;

//Numbers
protected
DIGIT: '0'..'9'
;

protected
NUMBER: ('+' | '-')? (DIGIT)+
;

protected
REAL_EXPONENT:
	('e' | 'E') NUMBER
;
 
protected
DOT:; 
 
protected
INTEGER:;   
    
REAL:
	(
		NUMBER {_ttype=INTEGER;}  
		( 
			(REAL_EXPONENT {_ttype=REAL;} | ('.' (DIGIT)* (REAL_EXPONENT)? {_ttype=REAL;}))  
		)?
	)
	|
	(
		'.' {_ttype=DOT;} (DIGIT (DIGIT)* (REAL_EXPONENT)? {_ttype=REAL;})? 
	)
;	

//Identifier and String
IDENTIFIER
	options {
		testLiterals=true;
		paraphrase = "an identifier";
	}
	:	('a'..'z'|'A'..'Z'|'_') ('a'..'z'|'A'..'Z'|'_'|'0'..'9')*
;

QUOTED_STRING: '\''! ( ~'\'' )* '\''!
;

//Comments
COMMENT: "//" (~('\n'|'\r'))*
    { $setType(Token.SKIP); }
;


////////////////////////
// Estela Parser 
////////////////////////

/**
 *
 *
 */
class EstelaParser extends Parser;
 
options {
    k = 4;
    buildAST = true;
    defaultErrorHandler = false;
}

tokens {
	AND_CONDITION;
	ARGUMENT_LIST;
	ASSIGN_VALUE;
	BOOLEAN_EXPRESSION;
	CLASS_MEMBER;
	CLASS_METHOD;
	CONDITIONAL_STATEMENT;
	DECLARE_CLASSIFIER;
	DECLARE_ELSE;
	DECLARE_ELSE_IF;
	DECLARE_FOR;
	DECLARE_IF;
	DECLARE_REFERENCE;
	EXPRESSION;
	FOR_BODY;
	METHOD_BODY;
	ID_VALUE;
	INVOKE_METHOD;
	RETURN_STATEMENT;
	STATEMENT;
	TERM;
	TYPE;
	VALUE;
}

program: ( (statement SEMI!) | COMMENT! )* EOF!
;

statement:
	( declareClassifier | declareReference )
    { #statement = #([STATEMENT, "statement"], #statement); }
;

declareReference:
	invokeMethod | 
	(type IDENTIFIER (assignValue)?)
    { #declareReference = #([DECLARE_REFERENCE, "declareReference"], #declareReference); }
;

invokeMethod:
	(IDENTIFIER OPEN_PAREN expression CLOSE_PAREN ) 
    { #invokeMethod = #([INVOKE_METHOD, "invokeMethod"], #invokeMethod); }
;

declareFor:
	FOR OPEN_PAREN IDENTIFIER ARRAY_OPEN IDENTIFIER ARRAY_CLOSE CLOSE_PAREN 
		OPEN_BRACKET (forBody)* CLOSE_BRACKET 
    { #declareFor = #([DECLARE_FOR, "declareFor"], #declareFor); }
;

conditionalStatement:
	declareIf (declareElseIf)* (declareElse)?
    { #conditionalStatement = #([CONDITIONAL_STATEMENT, "conditionalStatement"], #conditionalStatement); }
;

declareIf: 
	IF OPEN_PAREN booleanExpression CLOSE_PAREN OPEN_BRACKET (forBody)* CLOSE_BRACKET
    { #declareIf = #([DECLARE_IF, "declareIf"], #declareIf); }
;

declareElseIf:
	ELSE IF OPEN_PAREN booleanExpression CLOSE_PAREN OPEN_BRACKET (forBody)* CLOSE_BRACKET
    { #declareElseIf = #([DECLARE_ELSE_IF, "declareElseIf"], #declareElseIf); }
;

declareElse:
	ELSE OPEN_BRACKET (forBody)* CLOSE_BRACKET
    { #declareElse = #([DECLARE_ELSE, "declareElse"], #declareElse); }
;

forBody:
	( (declareReference SEMI!) | declareFor)
    { #forBody = #([FOR_BODY, "forBody"], #forBody); }
;

declareClassifier:
	CLASSIFIER IDENTIFIER OPEN_BRACKET ( classMember )* CLOSE_BRACKET
    { #declareClassifier = #([DECLARE_CLASSIFIER, "declareClassifier"], #declareClassifier); }
; 

//todo change to integer
type:
	(BOOLEAN | DOUBLE | DATASET | INSTANCE | IDENTIFIER | INT | STRING) (ARRAY_OPEN (DIM DOT_DOT REAL)? ARRAY_CLOSE)?
    { #type = #([TYPE, "type"], #type); }
; 

classMember:
	type IDENTIFIER ( classMethod | (assignValue SEMI!) | (SEMI!) )?
    { #classMember = #([CLASS_MEMBER, "classMember"], #classMember); }
; 

assignValue:
	ASSIGN expression
    { #assignValue = #([ASSIGN_VALUE, "assignValue"], #assignValue); }
;

expression:
	term
    { #expression = #([EXPRESSION, "expression"], #expression); }
;

booleanExpression:
	andCondition (OR andCondition)?
    { #booleanExpression = #([BOOLEAN_EXPRESSION, "booleanExpression"], #booleanExpression); }
;

andCondition:
    { #andCondition = #([AND_CONDITION, "andCondition"], #andCondition); }
;


term:
	value
    { #term = #([TERM, "term"], #term); }
;

value:
	(FALSE | TRUE | REAL | QUOTED_STRING | idValue | NULL | INTEGER)
    { #value = #([VALUE, "value"], #value); }
;

idValue:
	(SELF DOT)? IDENTIFIER (DOT IDENTIFIER)*
    { #idValue = #([ID_VALUE, "idValue"], #idValue); }
;
	 
	
classMethod:
	OPEN_PAREN (argumentList)?  CLOSE_PAREN OPEN_BRACKET (methodBody)* CLOSE_BRACKET
    { #classMethod = #([CLASS_METHOD, "classMethod"], #classMethod); }
;

methodBody:
	( (declareReference SEMI!) | (returnStatement SEMI!) | declareFor)
    { #methodBody = #([METHOD_BODY, "methodBody"], #methodBody); }
;
	
returnStatement:
	RETURN expression
    { #returnStatement = #([RETURN_STATEMENT, "returnStatement"], #returnStatement); }
;

argumentList:
	( (type IDENTIFIER) (COMMA type IDENTIFIER)* )
    { #argumentList = #([ARGUMENT_LIST, "argumentList"], #argumentList); }
;


////////////////////////
// Estela Walker 
////////////////////////

class EstelaWalker extends TreeParser;

program returns [EstelaProgram program]
{
	program = new EstelaProgram();
	List statements = new LinkedList();
}
: (statement[statements])*
{
	program.setStatements( statements );
}
;

statement [List statements] 
{

}
: #(STATEMENT ( invokeMethod[statements] | declareClassifier[statements] | declareReference[statements] )*)
{

}
;

declareReference [List statements]
{
	DataType dataType = null;
	Expression expr = null;
	
	InvokeMethod invokeMethod = null;
}
: #(
		DECLARE_REFERENCE
			((dataType = type()) id2AST:IDENTIFIER (expr=assignValue())?)
	) 
{
	DeclareReference declareReference = new DeclareReference();
	declareReference.setDataType(dataType);
	declareReference.setRefName(id2AST.getText());
	declareReference.setRefValue( expr );
	
	statements.add(declareReference);
}
;

invokeMethod [List statements]
{
	Expression expr = null;
	InvokeMethod invokeMethod = new InvokeMethod();
}
: #(INVOKE_METHOD (idAST:IDENTIFIER OPEN_PAREN (expr = expression()) CLOSE_PAREN) )
{
	invokeMethod.setName(idAST.getText());
	invokeMethod.addArgument(expr);
	statements.add(invokeMethod);
} 
;


declareClassifier [List statements]
{
	DeclareClassifier declareClassifier = new DeclareClassifier();
}
: #( DECLARE_CLASSIFIER CLASSIFIER idAST:IDENTIFIER OPEN_BRACKET (classMember[declareClassifier])* CLOSE_BRACKET )
{
	String classifierType = idAST.getText();
	declareClassifier.setClassifierType(classifierType);
	
	statements.add(declareClassifier);	
}
;

type returns [DataType dataType]
{
	dataType = new DataType();
}
:#(TYPE (booleanAST:BOOLEAN
		 | doubleAST:DOUBLE 
		 | datasetAST:DATASET 
		 | instanceAST:INSTANCE
		 | intAST:INT
		 | stringAST:STRING
		 | identifierAST:IDENTIFIER) (arrayOpenAST:ARRAY_OPEN (DIM DOT_DOT lengthAST:REAL)? ARRAY_CLOSE)? )
{
	if (doubleAST != null) {
		dataType.setType(DataType.DOUBLE);
	} else if (stringAST != null) {
		dataType.setType(DataType.STRING);
	} else if (booleanAST != null) {
		dataType.setType(DataType.BOOLEAN);
	} else if (datasetAST != null) {
		dataType.setType(DataType.DATASET);
	} else if (instanceAST != null) {
		dataType.setType(DataType.INSTANCE);
	} else if (intAST != null) {
		dataType.setType(DataType.INT);
	} else if (identifierAST != null) {
		dataType.setType(DataType.USER_DEFINED);
		dataType.setUserDefinedType(identifierAST.getText());
	}
	
	if (lengthAST != null) {
		dataType.setArrayDim((int) Double.parseDouble(lengthAST.getText()));
	} else {
		if (arrayOpenAST != null) {
			dataType.setArrayDim(1);
		}
	}
}
;	

classMember[DeclareClassifier declareClassifier]
{
	DataType dataType = null;
	Expression expr = null;
	ClassMethod method = null;
	
}
:#( CLASS_MEMBER (dataType = type()) 
		 id2AST:IDENTIFIER ( (expr=assignValue()) | (method=classMethod()) )? 

  ) //end CLASS_MEMBER
{
	if (method != null) { //class member is a method
		method.setReturnType(dataType);
		method.setName(id2AST.getText());
		declareClassifier.addMethod( method );	
	
	} else { //class member is a field
		ClassField field = new ClassField();
		field.setDataType(dataType);	
		field.setName(id2AST.getText());
		field.setValue( expr );
		declareClassifier.addField( field );
		
	}
} 
;

assignValue returns [Expression expr]
{
	expr = null; 
}
: #(ASSIGN_VALUE ASSIGN expr=expression())
{
	return expr;
}
;

expression returns [Expression expr]
{
	expr = null;
}
:#(EXPRESSION (expr=term()) )
{
}
;

term returns [Expression expr]
{
	expr = null;
}
:#(TERM (expr=value()) )
{

}
;

value returns [Expression value]
{
	value = null;
	IdentifierValue idValue = null;
}
:#(VALUE 
	(idValue=idValue() | trueAST:TRUE | falseAST:FALSE | realAST:REAL | quotedAST:QUOTED_STRING | nullAST:NULL | intAST:INTEGER) )
{
	if (idValue != null) {
		value = idValue;
	} else if (realAST != null) {
		value = new DoubleValue( Double.parseDouble(realAST.getText()) );
	} else if (trueAST != null) {
		value = new BooleanValue(true);
	} else if (intAST != null) {
		value = new IntegerValue( Integer.parseInt(intAST.getText()) ); 
	} else if (falseAST != null) {
		value = new BooleanValue(false);
	} else if (quotedAST != null) {
		value = new StringValue( quotedAST.getText() );
	} else if (nullAST != null) {
		value = NullValue.instance;
	}
}
;

idValue returns [IdentifierValue value]
{
	value = new IdentifierValue();
	List ids = new LinkedList();
}
:#(ID_VALUE (SELF DOT { value.setSelf(true); } )? 
		idAST:IDENTIFIER {ids.add(idAST.getText()); } 
			( DOT idAST2:IDENTIFIER {ids.add(idAST2.getText()); } )* )
{
	value.setIds( ids );
}
;

classMethod returns [ClassMethod method]
{
	method = new ClassMethod();
	List args = new LinkedList();
	List body = new LinkedList();
}
: #(CLASS_METHOD OPEN_PAREN (args = argumentList)? CLOSE_PAREN OPEN_BRACKET ( methodBody[body] )* CLOSE_BRACKET)
{
	method.setArgumentList(args);
	method.setBody(body);
} 
;

argumentList returns [List arguments]
{
	arguments = new LinkedList();
	DataType dataType = null;
}
: #(ARGUMENT_LIST ( 
		(dataType=type() idAST:IDENTIFIER { 
						DeclareReference ref = new DeclareReference();
						ref.setRefName(idAST.getText());
						ref.setDataType(dataType);
						arguments.add(ref);
						
			}) (COMMA dataType=type() id2AST:IDENTIFIER { 
						DeclareReference ref = new DeclareReference();
						ref.setRefName(id2AST.getText());
						ref.setDataType(dataType);
						arguments.add(ref);
			}  )* 
	) //end ARGUMENT_LIST 
) //end #(  
{

}
;

methodBody[List body]
{

}
:#(METHOD_BODY ( declareReference[body] | returnStatement[body] | declareFor[body]) )
{
	
}
;

declareFor[List body]
{
	List forBody = new LinkedList();
}
:#(DECLARE_FOR FOR OPEN_PAREN idAST:IDENTIFIER ARRAY_OPEN id2AST:IDENTIFIER ARRAY_CLOSE CLOSE_PAREN 
		OPEN_BRACKET (forBody[forBody])* CLOSE_BRACKET)
{
	DeclareFor declareFor = new DeclareFor();
	declareFor.setBody(forBody);
	
	body.add(declareFor);
}
;

forBody[List body]
{
}
:#(FOR_BODY  ( declareReference[body] | declareFor[body]) )
{
}
;

returnStatement[List body]
{
	Expression expr = null;
}
: #(RETURN_STATEMENT RETURN expr=expression() )
{
	ReturnExpression ret = new ReturnExpression();
	ret.setExpression(expr);
	body.add(ret);
}
;

